/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package camel.client;

import camel.server.admin.TeamMember;
import camel.server.session.CollaborationSession;
import camel.server.session.LobbyEvent;
import camel.server.session.LoginSessionToken;
import camel.server.session.ModelSemaphore;
import camel.server.session.ParticipantCursor;
import camel.server.session.SessionCommand;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
import javax.ejb.Remove;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 *
 * @author mac
 */
@Stateless
public class CamelBean implements CamelRemote, CamelLocal {
    // Add business logic below. (Right-click in editor and choose
    // "EJB Methods > Add Business Method" or "Web Service > Add Operation")
    
    private static final Logger logger = Logger.getLogger(
                "camel.client.CamelBean");
    
    private static final String serverKey = "SimpleKey";
    
    private String currentMethod;
    private String currentLogUsername;
    private SessionTicket currentTicket;
    private LoginSessionToken currentToken;
    
    @PersistenceContext
    private EntityManager em;
    
    
    private void setLoggingParameters(String methodName, SessionTicket ticket) {
        currentMethod = "<" + methodName + "> ";
        currentLogUsername = "[invalid](not logged in)";
        currentTicket = ticket;
        if (ticket != null) {
            if ( ticket.getUsername() != null) {
                currentLogUsername = "[" + ticket.getUsername() + "]";
            }
            if (ticket.isLogInSuccessful()) {
                currentLogUsername += "(logged in)";
            }
         }
    }
    
    @SuppressWarnings("unchecked")
    private void varifyUserLoggedIn() {
    	currentToken = null;
        if (currentTicket == null) {
            throw new IllegalArgumentException(currentMethod + " : ticket is null");
        }
        else {
            currentTicket.setLastOpSuccessful(false);

            List<LoginSessionToken> tokens;
            tokens = (List<LoginSessionToken>) em.createNamedQuery("findLoginToken")
        		.setParameter("sn", new Long(currentTicket.getSn()))
        		.setParameter("token", currentTicket.getToken())
            	.getResultList();
            
            if(tokens.size() == 1) {
            	// it seems to be valid
            	currentToken = tokens.get(0);
            	currentToken.touch();
            	currentToken.populateTicket(currentTicket);
            }
            else if (tokens.size() == 0) {
            	throw new  SecurityException(currentMethod + " : ticket is invalid (token not found)");
            }
            else {
            	throw new  SecurityException(currentMethod + " : ticket is invalid (more than one token found)");
            }
        }
    }
        
    private void varifyUserInSession() {
    	varifyUserLoggedIn();
        if(! currentTicket.isInSession() ) {
            throw new IllegalStateException(currentMethod + " : user is not in any session");
        }
        return;
    }
    
    @SuppressWarnings("unchecked")
    public void NotifyTeamStatusChange() {
    	
    	setLoggingParameters("NotifyTeamStatusChange", null);
    	logger.info(currentMethod + " : here");
    	
    	List<TeamMember> onlineMembers;
    	onlineMembers = (List<TeamMember>) em.createNamedQuery("findAllOnlineTeamMembers")
    		.setParameter("online", TeamMember.LoginState.online)
    		.setParameter("not_role", TeamMember.MemberRole.disabled)
        	.getResultList();
    	
    	// insert a notification event for each online member
    	for(TeamMember member : onlineMembers) {
        	LobbyEvent event = new LobbyEvent();
        	event.setTargetMember(member.getId());
        	
        	logger.info(currentMethod + " : inform " + member.getId());
        	
        	event.setTimeCreated(new Date());
        	ClientEventTeamStatusChange change = new ClientEventTeamStatusChange();
        	
        	event.setCmd(change);
        	em.persist(event);
    	}
    }

    public SessionTicket login(final ParticipantLoginInfo cred) {
        setLoggingParameters("login", null);
        SessionTicket ticket = new SessionTicket();
        ticket.setLogInSuccessful(false);
        try {
            TeamMember member = em.find(TeamMember.class, cred.getUsername());
            if ( member != null ) {
                if (member.IsPasswordMatch(cred.getPassword())) {
                    member.setLoginState(TeamMember.LoginState.online);
                    currentToken = new LoginSessionToken();
                    currentToken.setMember(member);
                    em.persist(currentToken);
                    
                    currentToken.generateToken ();
                    currentToken.populateTicket(ticket);
                }
            }
        } catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        
        setLoggingParameters("login", ticket);
        if (ticket.isLogInSuccessful()) {
            logger.info(currentMethod + " "+currentLogUsername + " login successful");
            NotifyTeamStatusChange();
        }
        else {
            logger.warning(currentMethod + currentLogUsername + " login failure");
        }
        return ticket;
    }
    
    private void LeaveSessionInternal (ParticipantCursor cursor, boolean shouldNotify) {
    	CollaborationSession session = cursor.getSession();
    	
    	// we have to preserve the color setting for this participant in this session,
    	// so we cannot remove the cursor altogether from the session
    	cursor.setActiveInSession(false);
		// remove the cursor
		//session.getCursors().remove(cursor);
		
		// if this user happens to be the facilitator of this session
		if (cursor.isFacilitator()) {
			cursor.setFacilitator(false);
			// find anyone (still) in this session and is NOT a playback observer
			boolean FoundActiveParticipant = false;
			for(ParticipantCursor participant : session.getCursors()) {
				if(participant.isActiveInSession() && (!participant.isSessionPlayback())) {
					// set him as the facilitator
					participant.setFacilitator(true);
					FoundActiveParticipant = true;
					break;
				}
			}
			// if we cannot find any one to set facilitator to, its not our fault. It only says there is no one left
			// we can leave this session for the garbage collector (not exist yet)
			if(FoundActiveParticipant == false) {
				// mark this session to be "closed" as no one can join, only playback is allowed
				session.setPlaybackOnly(true);
			}
		}
		
		// unlock all semaphores this user hold
		for(ModelSemaphore s : session.getSemaphores()) {
    		if(s.getOwner() == cursor) {
    			if(s.isLocked()) {
    				s.setLocked(false);
    				s.setOwner(null);
    				logger.info(currentMethod + " : semaphore " + s.getId() + " is released.");
    			}
    		}
    	}
		

		if(shouldNotify) {
			// notify everyone else in the session about this guy has left, and probably the facilitator has changed
			ClientEventSessionParticipantChange notify = new ClientEventSessionParticipantChange();
	        publishSessionManagementEvent(session, notify);
		}
    }
    
    @Remove
    @SuppressWarnings("unchecked")
    public void logout (SessionTicket ticket) {
        setLoggingParameters("logout", ticket);
        try {
            varifyUserLoggedIn();
            
            TeamMember member = em.find(TeamMember.class, ticket.getUsername());
            if ( member == null ) {
                // maybe the user has been removed from the database
                logger.warning(currentMethod + " : cannot find username");
            }
            else {
            	// find all cursors belong to this user (all the sessions this user is in)
            	List<ParticipantCursor> cursorList = (List<ParticipantCursor>) em.createNamedQuery("findAllCursorOfAMember")
            		.setParameter("member", member)
            		.getResultList();
        	
            	for(ParticipantCursor cursor : cursorList) {
            		// don't notify other participants if this guy is just a participant
            		LeaveSessionInternal(cursor, !cursor.isSessionPlayback());
            		// we have to preserve the color setting for this participant in this session,
                	// so we cannot remove the cursor altogether from the session
                    //em.remove(cursor);
            	}
            	
            	member.setLoginState(TeamMember.LoginState.offline);
            	currentToken.populateTicket(ticket);
            	ticket.setLastOpSuccessful(true);            
                
                // notify everyone still oneline about this guy has left
                NotifyTeamStatusChange();
                logger.info(currentLogUsername + " : logout successfully");
            }

        } catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
    }
    
    @SuppressWarnings("unchecked")
    public List<BriefSessionInfo> getSessionList (SessionTicket ticket) {
        setLoggingParameters("getSessionList", ticket);
        ArrayList<BriefSessionInfo> list = new ArrayList<BriefSessionInfo>();
        try {
            varifyUserLoggedIn();
            
            List<CollaborationSession> sessions = (List<CollaborationSession>) em.createNamedQuery("findAllSessions").getResultList();
                    //CollaborationSession.class, ticket.getUsername());
            if ( sessions == null ) {
                // maybe the user has been removed from the database
                logger.warning(currentMethod+": find session returns null");
            }
            else {
                for( CollaborationSession s : sessions) {
                    BriefSessionInfo info = new BriefSessionInfo();
                    if (s.getCreator() != null) {
                        info.setCreator(s.getCreator().getId());
                    }
                    else {
                        logger.warning(currentMethod+": session creator is null");
                    }
                    info.setDescription(s.getDescription());
                    info.setSessionId(s.getId());
                    info.setTimeCreated(s.getTimeCreated());
                    info.setTimeLastUpdated(s.getTimeLastUpdated());
                    info.setTitle(s.getTitle());
                    info.setPlaybackOnly(s.getPlaybackOnly());

                    if(s.getCursors() != null) {
                        for ( ParticipantCursor cursor : s.getCursors() ) {
                            if (cursor.getMemberAccount() != null ) {
                            	// only list those "active" participant cursors
                            	if(cursor.isActiveInSession()) {
                            		info.addParticipant(cursor.getMemberAccount().getId());
                            	}
                            }
                            else {
                                logger.warning(currentMethod+": some cursor does not have matching member");
                            }
                        }
                    }
                    list.add(info);
                }

            } 
        } catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return list;
    }
    
    private void publishSessionManagementEvent (CollaborationSession session, ClientEventManagement notify) {
        SessionCommand sessionCmd = new SessionCommand();
        sessionCmd.setCmd(notify);
        sessionCmd.setMasterSession(session);
        sessionCmd.setOriginator("system");
        em.persist(sessionCmd);
    }
    
    static Color[] colorArray = null;
    private void initializeColor () {
    	if(colorArray == null) {
    		// color value from http://htmlhelp.com/cgi-bin/color.cgi
    		colorArray = new Color[10];
        	colorArray[0] = new Color(0x00, 0x00, 0xFF); // blue
        	colorArray[1] = new Color(0xA5, 0x2A, 0x2A); // brown
        	colorArray[2] = new Color(0x22, 0x8B, 0x22); // forest green
        	colorArray[3] = new Color(0xDC, 0x14, 0x3C); // crimson
        	colorArray[4] = new Color(0x00, 0x00, 0x8B); // dark blue
        	colorArray[5] = new Color(0x8B, 0x00, 0x00); // dark red
        	colorArray[6] = new Color(0x80, 0x00, 0x80); // purple
        	colorArray[7] = new Color(0xFF, 0x00, 0x00); // red
        	colorArray[8] = new Color(0x2F, 0x4F, 0x4F); // dark slate gray
        	colorArray[9] = new Color(0x00, 0x64, 0x00); // dark green
    	}
    	else {
    		// shuffle the color array each time we are called
    		// so we will not see the same color so easily
    		Collections.shuffle(Arrays.asList(colorArray));
    	}
    }
    
    private void assignColor (CollaborationSession session, ParticipantCursor cursor) {
    	initializeColor ();
    	
    	for (Color c : colorArray) {
    		// look for unused color
    		boolean found = false;
	    	for (ParticipantCursor eachCursor : session.getCursors()) {
	    		// find the order of the given cursor in a given session
	    		if(eachCursor.getColor() != null) {
		    		if(eachCursor.getColor().equals(c)) {
		    			found = true;
		    			break;
		    		}
	    		}
	    		
	    	}
	    	if(found == false) {
				// use the predefined color
				cursor.setColor(c);
				logger.info(currentMethod+": assign predefined color " + c + " to " + cursor.getMemberAccount().getId());
				break;
    		}
    	}
    	if(cursor.getColor() == null) {
    		// we cannot use any predefined color (maybe there have been too any people)
    		// use random generated ones
    		Random random = new Random();
	    	int r = random.nextInt(200);
	    	int g = random.nextInt(200);
	    	int b = random.nextInt(200);
	    	int total = r + g+ b;
	    	if ( total > 400 ) {
	    		r = (r * 400) / total;
	    		g = (g * 400) / total;
	    		b = (b * 400) / total;
	    	}
	    	cursor.setColor(new Color(r, g, b));
	    	logger.warning(currentMethod+": assign random generated color to " + cursor.getMemberAccount().getId());
    	}
    }
    
    public SessionTicket joinSession (SessionTicket ticket, long sessionId, boolean isPlayback) {
        setLoggingParameters("joinSession", ticket);
        try {
            varifyUserLoggedIn();
            
            ticket.setInSession(false);
            
            CollaborationSession session = (CollaborationSession)em.find(CollaborationSession.class, sessionId);

            if (session == null) {
                logger.warning(currentMethod+": cannot find specified session id " + sessionId);
            }
            else {
                TeamMember member = em.find(TeamMember.class, ticket.getUsername());
                if ( member == null ) {
                    // maybe the user has been removed from the database
                    logger.warning(currentMethod+": cannot find user account in the ticket " + ticket.getUsername());
                }
                else {
                	
                	if(session.getPlaybackOnly() && (!isPlayback)) {
                		logger.warning(currentMethod+": this session is playback only. request rejected.");
                		// you cannot join a closed session unless you just want to play it back
                		ticket.setLastOpSuccessful(false);
                		return ticket;
                	}
                	
                	boolean isFacilitator = false;
                	Color oldColor = null;
                	
                	// flush all existing cursors of this member, in this session
                	// it seems that we only get a shallow copy of the "getCursors" list
                	// so the items we "remove" are really the one in that session
                	List<ParticipantCursor> oldList = new ArrayList<ParticipantCursor>(session.getCursors());
                	for (ParticipantCursor old : oldList) {
                		if (old.getMemberAccount().equals(member)) {
                			if(old.isFacilitator()) {
                				// preserve the facilitator role if possible
                				isFacilitator = true;
                			}
                			
                			// preserve color settings
                			oldColor = old.getColor();
                			
                			// unlock all semaphores this user hold before removing it
                			for(ModelSemaphore s : session.getSemaphores()) {
                	    		if(s.getOwner() == old) {
                	    			if(s.isLocked()) {
                	    				s.setLocked(false);
                	    				s.setOwner(null);
                	    				logger.info(currentMethod + " : semaphore " + s.getId() + " is released.");
                	    			}
                	    		}
                	    	}
                			
                			session.getCursors().remove(old);
                			em.remove(old);
                		}
                	}
                	
                    ParticipantCursor c = new ParticipantCursor();
                    c.setMemberAccount(member);
                    c.setSession(session);
                    c.setActiveInSession(true);
                    c.setSessionPlayback(isPlayback);
                    if(oldColor != null) {
                    	// preserve the color setting is possible
                    	c.setColor(oldColor);
                    }
                    else {
                    	assignColor(session, c);
                    }
                    
                    // a playback participant will never be the facilitator
                    if(!isPlayback) {
	                    //if this user is the first one into this session, set him to be the facilitator
	                	if ( session.getCursors().isEmpty()) {
	                		c.setFacilitator(true);
	                	}
	                	else {
	                		c.setFacilitator(isFacilitator);
	                	}
                    }
                    
                    em.persist(c);
                    session.getCursors().add(c);
                    currentToken.setCursor(c);
                    currentToken.setSession(session);
                    currentToken.populateTicket(ticket);
                    
                    // a playback participant will not alert other participants
                    if(!isPlayback) {
	                    ClientEventSessionParticipantChange notify = new ClientEventSessionParticipantChange();
	                    publishSessionManagementEvent(session, notify);
                    }
                    ticket.setLastOpSuccessful(true);
                }
            }
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return ticket;
    }
   
    public SessionTicket createSession (SessionTicket ticket, String title, String description) {
        setLoggingParameters("createSession", ticket);
        try {
            varifyUserLoggedIn();
            ticket.setLastOpSuccessful(false);
            CollaborationSession session = new CollaborationSession();

            session.setTitle(title);
            session.setDescription(description);
            session.setPlaybackOnly(false);

            em.persist(session);

            TeamMember member = em.find(TeamMember.class, ticket.getUsername());
            if ( member == null ) {
                // maybe the user has been removed from the database
                logger.warning(currentMethod+": cannot find user account for the creator");
            }
            else {
                session.setCreator(member);

                ParticipantCursor c = new ParticipantCursor();
                c.setMemberAccount(member);
                c.setSession(session);
                c.setActiveInSession(true);
                // this member is the default facilitator
                c.setFacilitator(true);
                assignColor(session, c);
                em.persist(c);
                session.getCursors().add(c);
                
                // create a default token for each session (test for white board multi-editing
                ModelSemaphore s = new ModelSemaphore();
            	s.setLocked(false);
            	s.setOwner(null);
            	s.setSession(session);
            	em.persist(s);
            	session.getSemaphores().add(s);

                currentToken.setCursor(c);
                currentToken.setSession(session);
                currentToken.populateTicket(ticket);
            }
            
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return ticket;
    }
    
    //TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    private void fillCommandsIntoTable (ParticipantCursor cursor, CommandPack postPack, CommandPack replyPack) {
        
        CollaborationSession session = cursor.getSession();
        if ( session == null ) {
            throw new IllegalStateException(currentMethod + " : cannot find session through cursor");
        }
        
        long lastPostedCmdId = -1;
                            
        for ( ClientEvent cmd : postPack.getCmdList()) {
            SessionCommand sessionCmd = new SessionCommand();
            
            //sessionCmd.setId(new Long((session.getCommands().size()+1)));            
            sessionCmd.setCmd(cmd);
            sessionCmd.setMasterSession(session);
            sessionCmd.setOriginator(replyPack.getTicket().getUsername());
            //session.getCommands().add(sessionCmd);
            
            em.persist(sessionCmd);
            
                        //lastPostedCmdId = sessionCmd.getId();
        }
        if(!postPack.getCmdList().isEmpty()) {
          cursor.setLastPostedCmdId(lastPostedCmdId);  
        }
    }

    @SuppressWarnings("unchecked")
    public CommandPack postCommandPack (CommandPack postPack ) {
        CommandPack replyPack = new CommandPack(postPack.getTicket());
        setLoggingParameters("postCommandPack", replyPack.getTicket());
        try {
        	varifyUserInSession();
            ParticipantCursor cursor = em.find(ParticipantCursor.class, replyPack.getTicket().getCursorId());
            if ( cursor == null ) {
                logger.warning(currentMethod + " : cannot find cursor");
            }
            else {
            	// anything an playback observer sends out would be silently filtered out
            	if(!cursor.isSessionPlayback()) {
	                // we may need to lock the session so we can get the size() right without interference from other concurrent users
	                fillCommandsIntoTable (cursor, postPack, replyPack);
            	}

                //em.lock(session, LockModeType.WRITE);
                /*
                List<SessionCommand> broadcastCmds = (List<SessionCommand>)em.createNamedQuery("findNotMyCommandInThisSession")
                        //.setParameter("sessionId", session.getId())
                        .setParameter("masterSession", session)
                        .setParameter("id", cursor.getLastReceivedCmdId())
                        .setParameter("me", replyPack.getTicket().getUsername())
                        .setMaxResults(10)
                        .getResultList();

                long lastReceivedCmdId = -1;
                for(SessionCommand scmd : broadcastCmds) {
                    replyPack.addCommand(scmd.getCmd());
                    lastReceivedCmdId = scmd.getId();
                }

                if(!replyPack.getCmdList().isEmpty()) {
                    cursor.setLastReceivedCmdId(lastReceivedCmdId);
                }

                replyPack.getTicket().setLastOpSuccessful(true);
                */
            }
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return replyPack;
    }
    
    @SuppressWarnings("unchecked")
    public CommandPack queryCommandPack (SessionTicket ticket, boolean filterOutEventsFromMe) {
        CommandPack replyPack = new CommandPack(ticket);
        setLoggingParameters("queryCommandPack", replyPack.getTicket());
        try {
        	varifyUserLoggedIn();
        	
        	// Lobby events, login/logout/invite
        	List<LobbyEvent> lobbyEvents;
        	lobbyEvents = (List<LobbyEvent>) em.createNamedQuery("findLobbyEventForMember")
            	.setParameter("member", ticket.getUsername())
            	.getResultList();
        	for(LobbyEvent lobby : lobbyEvents) {
        		ClientEvent event = (ClientEvent) lobby.getCmd();
                replyPack.addCommand(event);
                em.remove(lobby);
        	}
        	
        	// session events
        	if(ticket.isInSession()) {
	            ParticipantCursor cursor = em.find(ParticipantCursor.class, replyPack.getTicket().getCursorId());
	            if ( cursor == null ) {
	                //logger.warning(currentMethod + " : cannot find cursor");
	            }
	            else {
	                CollaborationSession session = cursor.getSession();
	                if ( session == null ) {
	                    throw new IllegalStateException(currentMethod + " : cannot find session through cursor");
	                }
	                List<SessionCommand> broadcastCmds;
	                
	                //logger.info(currentMethod + " : lastReceivedCmdId : " + cursor.getMemberAccount().getId() + " : " + cursor.getLastReceivedCmdId());
	                
	                if(filterOutEventsFromMe) {
		                broadcastCmds = (List<SessionCommand>)em.createNamedQuery("findNotMyCommandsInThisSession")
		                        .setParameter("masterSession", session)
		                        .setParameter("id", cursor.getLastReceivedCmdId())
		                        .setParameter("me", replyPack.getTicket().getUsername())
		                        .setMaxResults(10)
		                        .getResultList();
	                }
	                else {
		                broadcastCmds = (List<SessionCommand>)em.createNamedQuery("findAllCommandsInThisSession")
		                	.setParameter("masterSession", session)
		                	.setParameter("id", cursor.getLastReceivedCmdId())
		                	.setMaxResults(10)
		                	.getResultList();
	                }
	
	                long lastReceivedCmdId = -1;
	                for(SessionCommand scmd : broadcastCmds) {
	                	ClientEvent event = scmd.getCmd();
	                	// the server will enforce this field to be correct (not directly filled by the publisher)
	                	event.setOriginator(scmd.getOriginator());
	                    replyPack.addCommand(event);
	                    lastReceivedCmdId = scmd.getId();
	                }
	
	                //if(!replyPack.getCmdList().isEmpty()) {
	                if(!broadcastCmds.isEmpty()) {
	                    cursor.setLastReceivedCmdId(lastReceivedCmdId);
	                }
	            }
            }
        	replyPack.getTicket().setLastOpSuccessful(true);
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return replyPack;
    }
    
    public Boolean addUserAdHoc (final ParticipantLoginInfo cred) {
        
        try {
            logger.info(new String("CamelBean.addUser"));

            TeamMember member = em.find(TeamMember.class, cred.getUsername());
            if ( member != null ) {
                // maybe the user has been removed from the database
                logger.warning(new String("CamelBean.addUser : duplicated username"));
            }
            else {
                member = new TeamMember();
                member.setId(cred.getUsername());
                member.setPassword(cred.getPassword());
                em.persist(member);
                logger.info(new String("CamelBean.addUser : username = ") + member.getId());
                return true;
            } 

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return false;
    }

	@Override
	@SuppressWarnings("unchecked")
	public List<TeamMemberStatus> queryTeamMemberStatus(SessionTicket ticket) {
		List<TeamMemberStatus> list = new ArrayList<TeamMemberStatus>();
        setLoggingParameters("queryTeamMemberStatus", ticket);   
        try {
            varifyUserLoggedIn();
            List<TeamMember> members = (List<TeamMember>) em.createNamedQuery("findAllTeamMembers").getResultList();
            if ( members == null ) {
                // maybe the user has been removed from the database
                logger.warning(currentMethod+": find members returns null");
            }
            else {
               	 for( TeamMember tlist : members) {
	                    TeamMemberStatus tms = new TeamMemberStatus(tlist);
	                    list.add(tms);
                  }
            } 
        } catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return list;
    }

	@Override
	public List<ParticipantStatus> queryParticipantStatus(SessionTicket ticket) {
		List<ParticipantStatus> statusList = new ArrayList<ParticipantStatus>();
		
		setLoggingParameters("queryParticipantStatus", ticket);
		
		try {
			varifyUserInSession();
            
        	CollaborationSession session = (CollaborationSession)em.find(CollaborationSession.class, ticket.getSessionId());

            if (session == null) {
                logger.warning(currentMethod+": cannot find specified session id " + ticket.getSessionId());
            }
            else {
            	Collection<ParticipantCursor> participants = session.getCursors();
            	for(ParticipantCursor cursor : participants) {
            		if(cursor.isActiveInSession() && (!cursor.isSessionPlayback())) {
	            		ParticipantStatus status = new ParticipantStatus(cursor);
	            		statusList.add(status);
            		}
            	}
            }
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
		return statusList;
	}
	public SessionTicket  leaveSession (SessionTicket ticket, long sessionId) {
        setLoggingParameters("leaveSession", ticket);
        try {
        	varifyUserInSession();
           
            CollaborationSession session = (CollaborationSession)em.find(CollaborationSession.class, sessionId);
            if (session == null) {
                logger.warning(currentMethod+": cannot find specified session id " + sessionId);
            }
            else {
                TeamMember member = em.find(TeamMember.class, ticket.getUsername());
                if ( member == null ) {
                    // maybe the user has been removed from the database
                    logger.warning(currentMethod+": cannot find user account in the ticket " + ticket.getUsername());
                }
                else {
                	ParticipantCursor cursor = em.find(ParticipantCursor.class, ticket.getCursorId());
                	if( cursor == null) {
                		logger.warning(currentMethod+": cannot find the cursor for " + ticket.getUsername());
                	}
                	else {
                		// don't notify other participants if this guy is just an observer
	                	LeaveSessionInternal(cursor, !cursor.isSessionPlayback());
	                	// we have to preserve the color setting, so the cursor cannot be removed
	                	//em.remove(cursor);

	                	ticket.setInSession(false);
	                	ticket.setLastOpSuccessful(true);            
                	}
                }
            }
        } 
        catch (Exception ex) {
            logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
        }
        return ticket;
    }
	public SessionTicket assignRole (SessionTicket ticket, String username, String role){
		setLoggingParameters("assignRole", ticket);
        try {
        	varifyUserInSession();
        	
        	ParticipantCursor cursor = em.find(ParticipantCursor.class, ticket.getCursorId());
       	 	cursor.setFacilitator(false);
       	 	if (cursor == null)  {
       	 		throw new IllegalStateException(currentMethod + " : cannot find session participant cursor through sessionTicket");
       	 	}
       	 	
       	 	CollaborationSession session = (CollaborationSession)em.find(CollaborationSession.class, ticket.getSessionId());
       	 	if (session == null) {
       	 		logger.warning(currentMethod+": cannot find specified session id " + ticket.getSessionId());
       	 	}
       	 	else {	
	       	 	for (ParticipantCursor pc : session.getCursors()) {
	       	 		if (pc.getMemberAccount().getId().equals(username)) {
	       	 			pc.setFacilitator(true);
	      			}
	       	 	}
	       	 	// TODO: the global member role should not be mixed with the facilitator role(in a session)
	       	 	//ticket.setRole(TeamMember.MemberRole.admin); 
	       	 	//ticket.setRoleChange(true);
	       	 	ClientEventSessionParticipantChange notify = new ClientEventSessionParticipantChange();
		        publishSessionManagementEvent(session, notify);
       	 	}
       	 
        }catch(Exception ex)  {
       	 	logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
        return ticket;
	}

	@Override
	public SessionTicket invite(SessionTicket ticket, String userId) {
        setLoggingParameters("invite", ticket);
        try {
        	varifyUserInSession();
            ParticipantCursor cursor = em.find(ParticipantCursor.class, ticket.getCursorId());
            if ( cursor == null ) {
                logger.warning(currentMethod + " : cannot find cursor for originator");
            }
            else {
            	TeamMember member = (TeamMember)em.find(TeamMember.class, userId);
            	if ( member == null ) {
            		logger.warning(currentMethod + " : cannot find member for invitee");
            	}
            	else {
	            	LobbyEvent event = new LobbyEvent();
	            	event.setTargetMember(userId);
	            	event.setTimeCreated(new Date());
	            	ClientEventInvite invite = new ClientEventInvite();
	            	invite.setFacilitator(ticket.getUsername());
	            	invite.setSessionDesc(cursor.getSession().getDescription());
	            	invite.setSessionTitle(cursor.getSession().getTitle());
	            	invite.setSessionId(cursor.getSession().getId());
	            	
	            	event.setCmd(invite);
	            	em.persist(event);
	            	
	            	ticket.setLastOpSuccessful(true);
            	}
            }
    	}
        catch(Exception ex)  {
      	 logger.warning(currentMethod + " : exception");
           ex.printStackTrace();
           throw new RuntimeException (ex.getMessage());
        }
        return ticket;
	}
	 public Boolean userAdmin(String flag, ParticipantLoginInfo  info )  {
		  try {
	            logger.info(new String("CamelBean.userAdmin"));
	            TeamMember member = em.find(TeamMember.class, info.getUsername());
	            if (flag.equalsIgnoreCase("I"))  {
	            	if ( member != null ) {
	            		logger.warning(new String("CamelBean.userAdmin : duplicated username"));
	            		return false;
	            	}
	                else {
	                    member = new TeamMember();
	                    member.setId(info.getUsername());
	                    member.setPassword(info.getPassword());
	                    member.setMemberRole(TeamMember.MemberRole.user);
	                    em.persist(member);
	                }
	            } 
	            else if (flag.equalsIgnoreCase("U")) {
		            if ( member == null ) {
	            		logger.warning(new String("CamelBean.userAdmin : update error"));
	            		return false;
		            }
		            else {
		            	member.setPassword(info.getPassword());
		            }
	            }            
	            else if (flag.equalsIgnoreCase("R")) {
	            	if ( member == null ) {
            			logger.warning(new String("CamelBean.userAdmin : remove error"));
            			return false;
	            	}
	            	else {
	            		em.remove(member);
	            	}
            }
	        } catch (Exception ex) {
	            ex.printStackTrace();
	            throw new RuntimeException (ex.getMessage());
	        }
	        return true;
	}
	 
	 
	public int CreateSemaphore (SessionTicket ticket) {
		setLoggingParameters("CreateSemaphore", ticket);
        try {
        	varifyUserInSession();
        	
        	ParticipantCursor cursor = currentToken.getCursor();
        	CollaborationSession session = currentToken.getSession();
        	
        	ModelSemaphore s = new ModelSemaphore();
        	s.setLocked(true);
        	s.setOwner(cursor);
        	em.persist(s);
        	session.getSemaphores().add(s);
        	//return s.getId();
        	return 0;
       	 
        }catch(Exception ex)  {
       	 	logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
	}
	
	public SessionTicket ReleaseSemaphore (SessionTicket ticket, int id) {
		setLoggingParameters("ReleaseSemaphore", ticket);
        try {
        	varifyUserInSession();
        	
        	ParticipantCursor cursor = currentToken.getCursor();
        	CollaborationSession session = currentToken.getSession();
        	
        	ticket.setLastOpSuccessful(false);
        	for( ModelSemaphore s : session.getSemaphores()) {
        		//TODO: we only handle exactly one token for now
        		// if(s.getId() == id) {
        		{
					if(s.isLocked()) {
						s.setLocked(false);
						s.setOwner(null);
						ticket.setLastOpSuccessful(true);
					}
					else {
						ParticipantCursor owner = s.getOwner();
						logger.info(currentMethod + " : semaphore " + s.getId() + " is locked by " + owner.getMemberAccount().getId());
					}
        		}
        	}
        	return ticket;
       	 
        }catch(Exception ex)  {
       	 	logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
	}
	
	public SessionTicket TryAcquireSemaphore (SessionTicket ticket, int id) {
		setLoggingParameters("TryAcquireSemaphore", ticket);
        try {
        	varifyUserInSession();
        	
        	ParticipantCursor cursor = currentToken.getCursor();
        	CollaborationSession session = currentToken.getSession();
        	
        	ticket.setLastOpSuccessful(false);
        	for( ModelSemaphore s : session.getSemaphores()) {
        		//TODO: we only handle exactly one token for now
        		//if(s.getId() == id) {
        		{
					if(!s.isLocked()) {
						s.setLocked(true);
						s.setOwner(cursor);
						ticket.setLastOpSuccessful(true);
					}
					else if(s.isLocked() && s.getOwner() == currentToken.getCursor() ) {
						// if you already own the semaphore, lock doesn't do anything
						ticket.setLastOpSuccessful(true);
					}
					else {
						ParticipantCursor owner = s.getOwner();
						logger.info(currentMethod + " : semaphore " + s.getId() + " is locked by " + owner.getMemberAccount().getId());
					}
        		}
        	}
        	return ticket;
       	 
        }catch(Exception ex)  {
       	 	logger.warning(currentMethod + " : exception");
            ex.printStackTrace();
            throw new RuntimeException (ex.getMessage());
        }
	}
}
